package com.uum.blog.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.alibaba.fastjson.support.spring.PropertyPreFilters;
import com.uum.blog.pojo.entity.Blog;
import com.uum.blog.pojo.form.BlogForm;
import com.uum.blog.pojo.query.BlogPageQuery;
import com.uum.blog.service.EsBlogService;
import com.uum.web.util.JwtUtils;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import org.elasticsearch.action.bulk.BulkRequest;
import org.elasticsearch.action.bulk.BulkResponse;
import org.elasticsearch.action.delete.DeleteRequest;
import org.elasticsearch.action.get.GetRequest;
import org.elasticsearch.action.get.GetResponse;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.index.IndexResponse;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.action.update.UpdateRequest;
import org.elasticsearch.action.update.UpdateResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.text.Text;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.MatchPhraseQueryBuilder;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.reindex.BulkByScrollResponse;
import org.elasticsearch.index.reindex.UpdateByQueryRequest;
import org.elasticsearch.script.Script;
import org.elasticsearch.script.ScriptType;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.SortOrder;
import org.elasticsearch.search.suggest.Suggest;
import org.elasticsearch.search.suggest.SuggestBuilder;
import org.elasticsearch.search.suggest.SuggestBuilders;
import org.elasticsearch.search.suggest.SuggestionBuilder;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.concurrent.TimeUnit;

@Service
@RequiredArgsConstructor
public class EsBlogServiceImpl implements EsBlogService {

    private final RestHighLevelClient restHighLevelClient;

    @Value("${elastic.index.blog-info}")
    private String BLOG_INFO_INDEX;

    @Value("${elastic.index.blog-suggest}")
    private String BLOG_SUGGEST_INDEX;

    @Override
    public Map<String, Object> blogPage(BlogPageQuery pageQuery) throws Exception {

        SearchRequest searchRequest = new SearchRequest(BLOG_INFO_INDEX);
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();

        //构建分页条件
        //currentPage:1   from:0  size:5  [0,1,2,3,4]
        //currentPage:2   from:1  size:5  [5,6,7,8,9]
        //from = (currentPage-1)*size
        int currentPage = pageQuery.getCurrentPage();
        int pageSize = pageQuery.getPageSize();
        searchSourceBuilder.from((currentPage - 1) * pageSize);
        searchSourceBuilder.size(pageSize);
        searchSourceBuilder.sort("createTime", SortOrder.DESC);
        searchSourceBuilder.timeout(new TimeValue(10, TimeUnit.SECONDS));

        Boolean isRoot = JwtUtils.isAdministrator();

        //构建条件搜索
        BoolQueryBuilder boolQueryBuilder = createBuilder(pageQuery);
        boolQueryBuilder.filter(QueryBuilders.termQuery("isDeleted", 0));
        if (!isRoot) {
            boolQueryBuilder.filter(QueryBuilders.termQuery("userId", JwtUtils.getUserId()));
        }

        searchSourceBuilder.query(boolQueryBuilder);
        //执行搜索
        searchRequest.source(searchSourceBuilder);
        SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);

        //解析结果
        List<Map<String, Object>> list = new ArrayList<>();
        for (SearchHit documentFields : searchResponse.getHits().getHits()) {
            Map<String, Object> sourceAsMap = documentFields.getSourceAsMap();          //原来的数据
            list.add(sourceAsMap);
        }
        //返回数据
        Map resultMap = new HashMap();
        resultMap.put("total", searchResponse.getHits().getTotalHits().value);
        resultMap.put("source", list);
        System.out.println("sql==>" + searchRequest.source());
        return resultMap;
    }


    @Override
    @SneakyThrows
    public boolean add(BlogForm blog) {
        IndexRequest indexRequest = new IndexRequest(BLOG_INFO_INDEX);
        indexRequest.id(String.valueOf(blog.getId()));
        String jsonString = JSON.toJSONString(blog);
        indexRequest.source(jsonString, XContentType.JSON);  //保存的内容
        //执行操作
        IndexResponse add = restHighLevelClient.index(indexRequest, RequestOptions.DEFAULT);
        return add.getShardInfo().getFailed() > 0;

    }

    @SneakyThrows
    @Override
    public boolean update(BlogForm blogForm, Boolean isIgnore) {
        UpdateRequest request = new UpdateRequest(BLOG_INFO_INDEX, String.valueOf(blogForm.getId()));
        String jsonString;
        if (isIgnore) {
            //默认为null的也会转成json
            //转换时忽略"userId", "author", "createTime"这三个字段
            PropertyPreFilters filters = new PropertyPreFilters();
            jsonString = JSONObject.toJSONString(blogForm, filters.addFilter().addExcludes("userId", "author", "createTime"), SerializerFeature.PrettyFormat, SerializerFeature.WriteMapNullValue);
        } else {
            jsonString = JSONObject.toJSONString(blogForm);
        }
        System.out.println("esUpdate==>" + jsonString);
        request.doc(jsonString, XContentType.JSON);
        UpdateResponse update = restHighLevelClient.update(request, RequestOptions.DEFAULT);
        return update.getShardInfo().getFailed() > 0;
    }


    @SneakyThrows
    @Override
    public boolean settingBlog(Blog blog) {
        Long blogId = blog.getId();
        Integer isRelease = blog.getIsRelease();
        UpdateRequest request = new UpdateRequest(BLOG_INFO_INDEX, String.valueOf(blogId));
        Map map = new HashMap();
        map.put("isRelease", isRelease);
        map.put("isComment", blog.getIsComment());
        map.put("modifiedTime", DateUtil.formatLocalDateTime(blog.getModifiedTime())
        );
        request.doc(map, XContentType.JSON);
        UpdateResponse update = restHighLevelClient.update(request, RequestOptions.DEFAULT);
        //修改查询建议
        if (isRelease == 1) {
            insertSuggest(String.valueOf(blogId), blog.getTitle());
        } else {
            //删除查询建议
            deleteSuggest(ListUtil.toList(String.valueOf(blogId)));
        }
        return update.getShardInfo().getSuccessful() > 0;
    }

    @Override
    @SneakyThrows
    public boolean delete(List<String> idsList) {
        BulkRequest bulkRequest = new BulkRequest();
        for (String id : idsList) {
            UpdateRequest request = new UpdateRequest(BLOG_INFO_INDEX, String.valueOf(id));
            Map map = new HashMap();
            map.put("isDeleted", 1);
            request.doc(map, XContentType.JSON);
            bulkRequest.add(request);
        }
        BulkResponse bulk = restHighLevelClient.bulk(bulkRequest, RequestOptions.DEFAULT);
        boolean hasFailures = bulk.hasFailures();
        if (!hasFailures) {
            this.deleteSuggest(idsList);
        }
        return hasFailures;
    }

    @Override
    @SneakyThrows
    public List<Map<String, String>> listSuggest(String title) {
        List<Map<String, String>> result = new ArrayList<>();
        if (StrUtil.isBlank(title)) {
            return result;
        }
        //判断是否包含大写字母
        if (title.matches(".*[A-Z]+.*")) {
            title = title.toLowerCase();
        }
        List<String> suggestList = new ArrayList<>();
        SearchRequest searchRequest = new SearchRequest(BLOG_SUGGEST_INDEX);
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        /*
            {
              "_source": false,
              "suggest": {
                "blog_title_suggest": {
                  "prefix": "springboot",
                  "completion": {
                    "field": "title",
                    "size": 10,
                    "skip_duplicates": true
                  }
                }
              }
            }
         */
        SuggestionBuilder completionSuggestionBuilder = SuggestBuilders.completionSuggestion("title").prefix(title).size(10).skipDuplicates(true);
        SuggestBuilder suggestBuilder = new SuggestBuilder();
        suggestBuilder.addSuggestion("blog_title_suggest", completionSuggestionBuilder);
        searchSourceBuilder.suggest(suggestBuilder);
        searchRequest.source(searchSourceBuilder);
        SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
        System.out.println("suggestSql==>" + searchRequest.source());

        Suggest suggest = searchResponse.getSuggest();
        Suggest.Suggestion<? extends Suggest.Suggestion.Entry<? extends Suggest.Suggestion.Entry.Option>> blog_title_suggest = suggest.getSuggestion("blog_title_suggest");
        List<? extends Suggest.Suggestion.Entry<? extends Suggest.Suggestion.Entry.Option>> entries = blog_title_suggest.getEntries();
        for (Suggest.Suggestion.Entry<? extends Suggest.Suggestion.Entry.Option> entry : entries) {
            List<? extends Suggest.Suggestion.Entry.Option> options = entry.getOptions();
            for (Suggest.Suggestion.Entry.Option option : options) {
                suggestList.add(option.getText().toString());
            }
        }
        for (String s : suggestList) {
            Map<String, String> map = new HashMap();
            map.put("value", s);
            result.add(map);
        }
        return result;
    }

    @Override
    @SneakyThrows
    public boolean insertSuggest(String blogId, String title) {
        IndexRequest indexRequest = new IndexRequest(BLOG_SUGGEST_INDEX);
        indexRequest.id(blogId);
        Map<String, Object> jsonMap = new HashMap<>();
        jsonMap.put("title", title);
        indexRequest.source(jsonMap, XContentType.JSON);  //保存的内容
        //执行操作
        IndexResponse index = restHighLevelClient.index(indexRequest, RequestOptions.DEFAULT);
        int failed = index.getShardInfo().getFailed();
        return failed == 0;
    }

    @Override
    @SneakyThrows
    public boolean deleteSuggest(List<String> idsList) {
        BulkRequest bulkRequest = new BulkRequest();
        for (String id : idsList) {
            DeleteRequest deleteRequest = new DeleteRequest(BLOG_SUGGEST_INDEX, id);
            bulkRequest.add(deleteRequest);
        }
        BulkResponse bulk = restHighLevelClient.bulk(bulkRequest, RequestOptions.DEFAULT);
        return bulk.hasFailures();
    }

    @SneakyThrows
    @Override
    public Map webBlogPage(BlogPageQuery pageQuery) {
        String keyword = pageQuery.getKeyword();
        int currentPage = pageQuery.getCurrentPage();
        int pageSize = pageQuery.getPageSize();
        //构建条件搜索
        SearchRequest searchRequest = new SearchRequest(BLOG_INFO_INDEX);
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();

        //构建分页条件
        searchSourceBuilder.from((currentPage - 1) * pageSize);
        searchSourceBuilder.size(pageSize);
        searchSourceBuilder.sort("createTime", SortOrder.DESC);
        searchSourceBuilder.timeout(new TimeValue(60, TimeUnit.SECONDS));
        //构建查询条件
        BoolQueryBuilder boolQueryBuilder = createBuilder(pageQuery);
        boolQueryBuilder.filter(QueryBuilders.termQuery("isRelease", 1));
        boolQueryBuilder.filter(QueryBuilders.termQuery("isDeleted", 0));
        if (StrUtil.isNotBlank(keyword)) {
            keyword = keyword.trim();
            //构建高亮条件
            HighlightBuilder highlightBuilder = new HighlightBuilder();
            //高亮标题
            HighlightBuilder.Field highlightTitle = new HighlightBuilder.Field("title");
            highlightTitle.preTags("<span style='color:red'>");
            highlightTitle.postTags("</span>");
            highlightBuilder.field(highlightTitle);
            //高亮内容
            HighlightBuilder.Field highlightContent = new HighlightBuilder.Field("content");
            highlightContent.preTags("<span style='color:red'>");
            highlightContent.postTags("</span>");
            highlightBuilder.field(highlightContent);
            searchSourceBuilder.highlighter(highlightBuilder);

            //匹配
            MatchPhraseQueryBuilder mpq1 = QueryBuilders
                    .matchPhraseQuery("title", keyword);
            MatchPhraseQueryBuilder mpq2 = QueryBuilders
                    .matchPhraseQuery("content", keyword);
            boolQueryBuilder
                    .should(mpq1)
                    .should(mpq2)
                    .minimumShouldMatch(1);
        }
        searchSourceBuilder.query(boolQueryBuilder);
        //执行搜索
        searchRequest.source(searchSourceBuilder);
        SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);

        //解析结果
        List<Map<String, Object>> list = new ArrayList<>();
        for (SearchHit documentFields : searchResponse.getHits().getHits()) {
            HighlightField title = documentFields.getHighlightFields().get("title");
            HighlightField content = documentFields.getHighlightFields().get("content");    //高亮后的数据
            Map<String, Object> sourceAsMap = documentFields.getSourceAsMap();          //原来的数据
            //截取0-300
            String contentStr = (String) sourceAsMap.get("content");
            sourceAsMap.put("content", StrUtil.sub(contentStr, 0, 300));
            //解析高亮数据，替换掉原来的数据
            if (title != null) {
                Text[] fragments = title.getFragments();
                String newTitle = "";
                for (Text fragment : fragments) {
                    newTitle += fragment;
                }
                sourceAsMap.put("title", newTitle);
            }
            if (content != null) {
                Text[] fragments = content.getFragments();
                String newContent = "";
                for (Text fragment : fragments) {
                    newContent += fragment;
                }
                sourceAsMap.put("content", newContent);
            }
//            String htmlContent = (String) sourceAsMap.get("content");
//            sourceAsMap.put("content", MarkdownUtils.markdownToHtmlExtensions(htmlContent));
            list.add(sourceAsMap);
        }
        //返回数据
        Map resultMap = new HashMap();
        resultMap.put("total", searchResponse.getHits().getTotalHits().value);
        resultMap.put("source", list);
        System.out.println("webSql==>" + searchRequest.source());
        return resultMap;
    }

    @SneakyThrows
    @Override
    public List<Map<String, Object>> listHotBlog() {
        List<Map<String, Object>> resultList = new ArrayList<>();
        //构建条件搜索
        SearchRequest searchRequest = new SearchRequest(BLOG_INFO_INDEX);
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        searchSourceBuilder.fetchSource(new String[]{"id","title"}, new String[]{});
        searchSourceBuilder.from(0);
        searchSourceBuilder.size(8);
        searchSourceBuilder.sort("clickNum", SortOrder.DESC);
        searchSourceBuilder.timeout(new TimeValue(60, TimeUnit.SECONDS));

        BoolQueryBuilder queryAllBuilder = QueryBuilders.boolQuery().mustNot(QueryBuilders.termQuery("isDeleted", 1));
        searchSourceBuilder.query(queryAllBuilder);
        //执行搜索
        searchRequest.source(searchSourceBuilder);
        SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
        //解析结果
        for (SearchHit documentFields : searchResponse.getHits().getHits()) {
            Map<String, Object> sourceAsMap = documentFields.getSourceAsMap();
            resultList.add(sourceAsMap);
        }

        return resultList;
    }

    @SneakyThrows
    @Override
    public Map<String, Object> detail(Long id) {
        GetRequest request = new GetRequest(BLOG_INFO_INDEX, id.toString());
        GetResponse response = restHighLevelClient.get(request, RequestOptions.DEFAULT);
        return response.getSourceAsMap();
    }

    @SneakyThrows
    @Override
    public boolean updateAuthorName(Long userId, String nickname) {
        UpdateByQueryRequest request = new UpdateByQueryRequest("blog-info");
        //默认情况下，版本冲突会中止该UpdateByQueryRequest过程
        //设置冲突下继续执行
        request.setConflicts("proceed");
        request.setQuery(QueryBuilders.termQuery("userId", userId));
        //默认最多一次修改1000条
        request.setBatchSize(1000);
        request.setScript(
                new Script(
                        ScriptType.INLINE, "painless",
                        "ctx._source['author'] = '" + nickname + "'",
                        Collections.emptyMap()));
        BulkByScrollResponse response = restHighLevelClient.updateByQuery(request, RequestOptions.DEFAULT);
        return response.getStatus().getUpdated() > 0;
    }


    private BoolQueryBuilder createBuilder(BlogPageQuery pageQuery) {
        String title = pageQuery.getTitle();
        Integer isRelease = pageQuery.getIsRelease();
        Long categoryId = pageQuery.getCategoryId();
        Integer isComment = pageQuery.getIsComment();
        List<Long> tagIds = pageQuery.getTagIds();
        String startTime = pageQuery.getStartTime();
        String endTime = pageQuery.getEndTime();

        //构建条件搜索
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
//        boolQueryBuilder.must(QueryBuilders.termQuery("isDeleted", 0));

        if (StrUtil.isNotBlank(title)) {
            boolQueryBuilder.must(QueryBuilders.matchPhraseQuery("title", title).slop(3));
        }

        if (isRelease != null) {
            boolQueryBuilder.filter(QueryBuilders.termQuery("isRelease", isRelease));
        }

        if (isComment != null) {
            boolQueryBuilder.filter(QueryBuilders.termQuery("isComment", isComment));
        }

        if (categoryId != null) {
            boolQueryBuilder.filter(QueryBuilders.termQuery("categoryId", categoryId));
        }

        if (CollUtil.isNotEmpty(tagIds)) {
            BoolQueryBuilder tagBuilder = new BoolQueryBuilder();
            for (Long tagId : tagIds) {
                tagBuilder.must(QueryBuilders.matchQuery("tagIds", tagId));
            }
            boolQueryBuilder.filter(tagBuilder);
        }

        if (StrUtil.isNotBlank(startTime) && StrUtil.isNotBlank(endTime)) {
            boolQueryBuilder.filter(QueryBuilders.rangeQuery("createTime").gte(startTime).lte(endTime).format("yyyy-MM-dd HH:mm:ss"));
        }

        return boolQueryBuilder;
    }
}
